package org.xx.armory.rpc.decorators;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.bindings.BoundParamCollection;
import org.xx.armory.db.DataSourceStorage;
import org.xx.armory.db.DbContext;
import org.xx.armory.db.SessionFactory;
import org.xx.armory.db.TransactionManager;
import org.xx.armory.db.TransactionProperties;
import org.xx.armory.db.impl.ThreadContextTransactionManager;
import org.xx.armory.rpc.Decorator;
import org.xx.armory.rpc.Transactional;
import org.xx.armory.service.Invocation;
import org.xx.armory.service.ServiceContext;

import javax.sql.DataSource;

import static org.xx.armory.commons.Validators.notBlank;

/**
 * 用于启用环境事务的修饰器。
 * TODO: 分为2个修饰器，分别用于注入SessionFactory和事务。
 */
public abstract class AbstractTransactionDecorator
        extends AbstractDecorator
        implements Decorator {
    private static final String TRANSACTION_MANAGER_KEY = "__transactionManager__";
    private static final String SESSION_FACTORY_KEY = "__sessionFactory__";

    private final Logger logger = LoggerFactory.getLogger(AbstractTransactionDecorator.class);
    private DataSource dataSource;
    private TransactionManager transactionManager;
    private boolean serializable;
    private String dataSourceName;

    /**
     * 获取数据源的名字。
     *
     * @return 数据源的名字。
     */
    public final String getDataSourceName() {
        return dataSourceName;
    }

    /**
     * 设置数据源的名字。
     *
     * @param dataSourceName
     *         数据源的名字。
     * @throws IllegalArgumentException
     *         如果参数{@code dataSourceName}是{@code null}或者只包含空白字符。
     */
    public final void setDataSourceName(
            String dataSourceName
    ) {
        assertNotInitialized();

        this.dataSourceName = notBlank(dataSourceName, "dataSourceName").trim();
    }

    public final boolean isSerializable() {
        return serializable;
    }

    public final void setSerializable(
            boolean serializable
    ) {
        this.serializable = serializable;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize()
            throws Exception {
        super.initialize();

        // 加载数据源。
        this.dataSource = DataSourceStorage.getDataSource(this.dataSourceName);

        // 创建事务管理器。
        this.transactionManager = new ThreadContextTransactionManager();
        this.transactionManager.setDataSource(dataSource);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose()
            throws Exception {
        // 关闭事务管理器。
        if (this.transactionManager instanceof AutoCloseable) {
            try {
                ((AutoCloseable) this.transactionManager).close();
            } catch (Exception ex) {
                logger.warn("cannot close transaction manager", ex);
            }
        }

        // 关闭数据源。
        if (this.dataSource instanceof AutoCloseable) {
            try {
                ((AutoCloseable) this.dataSource).close();
            } catch (Exception ex) {
                logger.warn("cannot close data source", ex);
            }
        }

        super.dispose();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void before(
            Invocation invocation,
            BoundParamCollection params,
            ServiceContext serviceContext
    )
            throws Exception {
        final Transactional transactional = invocation.getMethod().getAnnotation(Transactional.class);
        if (transactional == null) {
            // 不需要进行事务管理。
        } else {
            final TransactionManager scopedTransactionManager = this.transactionManager;
            final TransactionProperties transactionProperties = new TransactionProperties(
                    false, // 范围事务，禁止自动提交。
                    transactional.readOnly(),
                    transactional.serializable());

            scopedTransactionManager.setProperties(transactionProperties);

            serviceContext.put(TRANSACTION_MANAGER_KEY, scopedTransactionManager);
        }

        before2(invocation, params, serviceContext);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object done(
            Invocation invocation,
            BoundParamCollection params,
            ServiceContext serviceContext,
            Object result
    ) {
        final TransactionManager scopedTransactionManager = (TransactionManager) serviceContext.get(TRANSACTION_MANAGER_KEY);
        if (scopedTransactionManager != null) {
            // 如果执行方法成功，并且启用了事务管理，需要提交事务。
            if (this.transactionManager != null) {
                this.transactionManager.current().commit();
            }
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void always(
            Invocation invocation,
            BoundParamCollection params,
            ServiceContext serviceContext
    )
            throws Exception {
        try {
            final SessionFactory sessionFactory = (SessionFactory) serviceContext.get(SESSION_FACTORY_KEY);
            if (sessionFactory != null) {
                // 如果注入了会话工厂，需要关闭会话工厂。
                sessionFactory.close();
            }
        } finally {
            final TransactionManager scopedTransactionManager = (TransactionManager) serviceContext.get(TRANSACTION_MANAGER_KEY);
            if (scopedTransactionManager != null) {
                // 如果启用了事务管理，需要关闭当前事务。
                scopedTransactionManager.current().close();
            }
        }
    }

    private void before2(
            Invocation invocation,
            BoundParamCollection params,
            ServiceContext serviceContext
    )
            throws Exception {
        if (invocation.getObject() instanceof DbContext) {
            // 注入事务管理器。
            final SessionFactory sessionFactory = createSessionFactory();
            final TransactionManager scopedTransactionManager = (TransactionManager) serviceContext.get(TRANSACTION_MANAGER_KEY);
            if (scopedTransactionManager != null) {
                sessionFactory.setTransactionManager(scopedTransactionManager);

                sessionFactory.initialize();

                // 注入会话工厂。
                ((DbContext) invocation.getObject()).setSessionFactory(sessionFactory);

                serviceContext.put(SESSION_FACTORY_KEY, sessionFactory);
            }
        }
    }

    /**
     * 创建会话工厂。
     *
     * @return 已创建的会话工厂。
     */
    protected abstract SessionFactory createSessionFactory();
}
